Entity Framework 中 DateTime 時區問題與解決方案
TLDR
DateTime的Kind屬性(Local, Utc, Unspecified)會影響ToLocalTime()與ToUniversalTime()的轉換結果,若處理不當將導致時間偏差。- 從資料庫讀取
DateTime時,因資料庫欄位不儲存時區資訊,EF Core 預設會將其Kind設為Unspecified,導致前端顯示時間錯誤。 - 解決方案應在資料存取層處理,而非在前端補上
Z字元。 - 推薦使用 EF Core 的
ValueConverter或.NET 6的ConfigureConventions()全域設定,自動將讀取出的時間強制標記為Utc。
DateTime 的時區格式問題
當開發者不清楚 DateTime.Kind 的狀態時,直接呼叫 ToLocalTime() 或 ToUniversalTime() 會產生非預期的時間偏移。
什麼情況下會遇到這個問題:當系統混用不同來源的 DateTime 物件,且未統一檢查或轉換 Kind 屬性時。
Kind為Local:呼叫ToLocalTime()不會改變時間。Kind為Utc:呼叫ToUniversalTime()不會改變時間。Kind為Unspecified:系統會假設該時間為 UTC 並轉換為本機時間(增加偏移),或假設為本機時間並轉換為 UTC(減少偏移)。
為了避免此類問題,建議參考如 ABP.IO 等框架的做法,定義統一的 IClock 介面來標準化時間轉換,確保 Kind 符合預期。
Entity Framework 使用 DateTime 的時區問題
使用 datetime 或 datetime2 等不含時區的資料庫類型時,EF Core 取出的資料 Kind 預設為 Unspecified。這導致序列化給前端時,時間字串末尾缺少 Z 標記,進而引發前端顯示的時間與預期不符。
什麼情況下會遇到這個問題:使用 Code First 或反向工程產生 Entity,且資料庫欄位未包含時區資訊時。
解決方案:使用 ValueConverter 自動轉換
透過 ValueConverter,可以在資料寫入資料庫前確保為 UTC,並在讀取時強制指定 Kind 為 Utc。
定義轉換器類別:
csharp
public class UtcDateTimeValueConverter : ValueConverter<DateTime, DateTime> {
public UtcDateTimeValueConverter()
: base(v => ToDb(v), v => FromDb(v)) {
}
private static DateTime ToDb(DateTime dateTime) {
return dateTime.Kind == DateTimeKind.Utc ? dateTime : dateTime.ToUniversalTime();
}
private static DateTime FromDb(DateTime dateTime) {
return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
}
}全域套用設定
若要避免逐一設定屬性,建議在 DbContext 中使用 ConfigureConventions(.NET 6 以上版本)進行全域配置:
csharp
public partial class MyDbContext : DbContext {
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) {
ArgumentNullException.ThrowIfNull(configurationBuilder);
configurationBuilder.Properties<DateTime>().HaveConversion<UtcDateTimeValueConverter>();
}
}若無法使用 ConfigureConventions,則可透過 OnModelCreating 遍歷所有屬性並套用轉換器:
csharp
partial void OnModelCreatingPartial(ModelBuilder modelBuilder) {
foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) {
foreach (IMutableProperty property in entityType.GetProperties()) {
if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?)) {
property.SetValueConverter(typeof(UtcDateTimeValueConverter));
}
}
}
}異動歷程
- 2024-08-15 初版文件建立。
